// locutor
// (c) 2009, Normen Müller, normen.mueller@gmail.com  
// $Id: D.scala 86 2010-05-11 21:32:13Z normen.mueller $
package locutor.g

import java.io.File
import java.net.URL

import scala.xml._
import scala.xml.XML.{loadFile}
import scala.xml.Utility.{trim}

import scaup.Views._

import scalaxx.path._
import scalaxx.path.LocationPath
import scalaxx.diff.Command

import locutor.rws.gm._
import locutor.DocumentModel
import locutor.g.parsing.DependencyGraphParser

//@todo better handling of attributes; maybe sth. like
//case class Attribute[T](key: String, value: T)(implicit m: scala.reflect.Manifest[T]) {
//  def toGrShell = key + "=" + m.toString match {
//    case primitive if primitive endsWith "Boolean" | "Integer" | "Float" | "Double" => value.toString
//    case primitive if primitive endsWith "String                                    => "\"" + value + "\""
//    case set       if set endsWith "[java.lang.String]"                             => ""
//  }
//}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
abstract class Attribute {  
  override def toString = toGrShell  
  def toGrShell: String
}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
case class SimpleAttribute(val key: String, val value: Option[String]) extends Attribute {
  def toGrShell = key + (value match {case Some(v) => "="+v case _ => ""})
}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
case class StringAttribute(val key: String, val value: String) extends Attribute {
  def toGrShell = key + "=\"" + value + "\""
}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
case class SetAttribute(val key: String, val typ: String, val value: Set[String]) extends Attribute {
  def toGrShell = key + "=set<" + typ + ">{" + (typ match {
    case "string" => (value map (v => "\"" + v + "\"")).toList mkString ","
    case _        => value mkString ","    
  }) + "}"
}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
case class MapAttribute(val key: String, val keyTyp: String, val valTyp: String, val value: Map[String,String]) extends Attribute {
  def toGrShell = key + "=map<" + keyTyp + "," + valTyp + ">{" + (value map (_ match {
    case (k,v) => (if(keyTyp == "string") "\"" + k + "\"" else k) + " -> " + (if(valTyp == "string") "\"" + v + "\"" else v)
  })) + "}"
}

/**  
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 * 
 *  @todo semantic Infom might neither have an URL nor an XPath; maybe we want to introduce sth. like SemanticInfom and StructuralInfom, respectively.
 */
case class Infom(val label: String, val typ: String, val url: Option[URL], val path: Option[LocationPath], val attrs: List[Attribute]) {
  def this(typ: String, url: Option[URL], path: Option[LocationPath], attrs: List[Attribute]) =
    this((url, path) match {
      case (Some(url), Some(path)) => url.toString + "|" + path
      case _                       => "$"+DependencyGraph.nextLabel
    }, typ, url, path, attrs)
  
  override def toString = toGrShell
  
  def toGrShell = 
    ( "new :" + typ + "($=\""+ label + "\""
    + (url match {case Some(u) => ",url=\"" + u + "\"" case _ => ""}) 
    + (path match {case Some(p) => ",path=\"" + p + "\"" case _ => ""}) 
    + (if(!(attrs isEmpty)) attrs mkString (",", ",", ")") else ")")
    )
}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
case class Link(val source: Infom, val typ: String, val target: Infom, val attrs: List[Attribute]) {
  override def toString = toGrShell
  
  def toGrShell = "new @(\"" + source.label + "\") -:" + typ + (if(!(attrs isEmpty)) attrs mkString ("(", ",", ")-> ") else "-> ") + "@(\"" + target.label + "\")"
}

/** Companion to <code>DependencyGraph</code> class.
 * 
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
object DependencyGraph {
  /** Semantic node labels.
   * 
   *  @todo As semantic nodes neither have an url nor a path attribute, the labels have to be unique for later identification (cf. ImpactGraph#mark).
   */
  private val randomizer = new scala.util.Random(1)
  
  private [g] def nextLabel = randomizer.nextInt
  
  def empty = new DependencyGraph(Nil, Nil)
  
  def parse(description: Option[File]): Option[DependencyGraph] = description match {
    case Some(description) => Some(parse(description))
    case None              => None
  }
  
  def parse(description: File): DependencyGraph = DependencyGraphParser.parse(description)
  
  /**
   *  @todo validate wrt. the corresponding graph model G, i.e. each node/ link type has to be defined within G
   *  @todo refactor computation of structural dependency graphs
   *  @todo make computation engine of structural dependency graphs parametrizable such that 3rd parties can 
   *    provide their own enginies.
   *  @todo set up a JAVA use case for computation of a structural dependency graph based on JAVA source files;
   *    therefore reassess the idea of using JAVA annotations for improved dependency computation; the computation
   *    engine would consider specific locutor annotations provided in a seperate locutor-annotations library; each
   *    project including this library could use these annotations without tremendous code interfere; the computation
   *    enginge provided by the locutor core library could utilize these annotations for an improved dependency 
   *    computation;  this workflow, however, only applies for a standalone MoC application, i.e., for 
   *    example, an locutor eclipse plug-in doesn't need those annotations due to the fact eclipse already provides
   *    an sophisticated dependency graph on JAVA project files (I am just guessing regarding eclipse, though).
   */
  def compute(document: File): DependencyGraph = structural(document.toURL, loadFile(document), Root childAt 1) match {
    case (nodes, edges) => new DependencyGraph(nodes, edges)
  }
  
  private def structural(url: URL, elem: Elem, path: LocationPath): (List[Infom], List[Link]) = trim(elem) match {
    case Elem(_, _, _, _, cs@_*) => ((dependant: Infom, providers: List[Infom]) => 
      structural(url, cs, path childAt 1) match {case (ns, es) => (dependant :: ns, (providers map (Link(_, "childOf", dependant, Nil))) ::: es)}
    )(infom(url, elem, path), infoms(url, cs, path childAt 1))      
    case _ => (Nil, Nil)
  }
  
  private def structural(url: URL, elems: Seq[Node], path: LocationPath): (List[Infom], List[Link]) = elems match {
    case (e: Text) :: tl => structural(url, tl, path++) match {case (ns, es)   => (infom(url, e, path text) :: ns, es)} 
    case (e: Elem) :: tl => structural(url, e, path)    match {case (ens, ees) => structural(url, tl, path++) match {case (ns, es) => (ens ::: ns, ees ::: es)}}
    case _ :: tl         => structural(url, tl, path)
    case Nil             => (Nil, Nil)
  }
  
  private def infom(url: URL, elem: Node, path: LocationPath): Infom = elem match {
    case Text(content)             => new Infom("_text_", Some(url), Some(path), List[Attribute](StringAttribute("content", content)))
    case Elem(_, label, as, _, _*) => new Infom(label, Some(url), Some(path), (List[Attribute]() /: as)((l,a) => StringAttribute(a.key, a.value.text) :: l))
  }
  
  private def infoms(url: URL, elems: Seq[Node], path: LocationPath): List[Infom] = elems match {
    case (e: Text) :: tl => infom(url, e, path text) :: infoms(url, tl, path++)
    case (e: Elem) :: tl => infom(url, e, path) :: infoms(url, tl, path++)
    case _ :: tl         => infoms(url, tl, path)
    case Nil             => Nil
  }  
}

/** A dependency graph.
 * 
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
class DependencyGraph private [locutor] (val nodes: List[Infom], val edges: List[Link]) {
  def join(that: DependencyGraph): DependencyGraph = new DependencyGraph(nodes ::: that.nodes, edges ::: that.edges)
  
  override def toString = toGrShell
  
  def toGrShell = (nodes ::: edges) mkString ("\n", "\n", "\n")
}
